package com.dkm.interceptor;

import com.dkm.commons.Response;
import com.dkm.constant.CommonErrorEnum;
import com.dkm.constant.Constant;
import com.dkm.modules.sys.menu.model.MenuPO;
import com.dkm.modules.sys.menu.service.MenuService;
import com.dkm.modules.sys.user.model.User;
import com.dkm.modules.sys.user.service.UserService;
import com.dkm.util.redis.RedisUtils;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import redis.clients.jedis.exceptions.JedisException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author lizi
 * @Description 在访问controller前判断是否登录，返回json，不进行重定向。
 * @return true-继续往下执行，false-该filter过滤器已经处理，不继续执行其他过滤器
 * @date 2019/8/20 上午11:19
 * @Version 1.0
 */
@Component
@Aspect
@Slf4j
public class SecurityAuthInterceptor {


    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private Environment env;

    @Autowired
    private UserService userService;

    @Autowired
    private MenuService menuService;

    @Value("${open-permission-intercept}")
    private boolean openPermissionIntercept;

    @Value("${remote.prefix}")
    private String tokenPrefix;


    /**
     * 无需拦截的地址
     */
    private static final List<String> UN_INTERCEPTOR = Lists.newArrayList("/index", "export", "/refundResult");


    @Pointcut("execution(* com.dkm.*.*.*.controller.*.*(..)) ")
    public void loginJudgementControllerMethodPointcut() {
        log.debug("loginJudgementControllerMethodPointcut用户是否登陆切面");
    }

    @SuppressWarnings("unchecked")
    @Around("loginJudgementControllerMethodPointcut()")
    public Object loginInterceptor(ProceedingJoinPoint pjp) throws Throwable {
        //获取请求uri
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String requestURI = request.getRequestURI();
        boolean unInterceptor = this.isUnInterceptor(requestURI);
        if (unInterceptor) {
            return pjp.proceed();
        }

        Object[] args = pjp.getArgs();

        // 登录无需拦截
        Response<Object> returnMsg = new Response<>();
        String methodName = pjp.getSignature().getName();
        if (methodName.startsWith(Constant.METHOD_NAME_STARTSWITH_LOGIN)) {
            returnMsg = (Response<Object>) pjp.proceed(args);
            log.debug("OUT Response------" + methodName + ":" + returnMsg);
            return returnMsg;
        }

        // 校验token
        Response x = validateToken(request);
        if (Objects.nonNull(x)) {
            return x;
        }

        // todo:校验用户权限
        if (openPermissionIntercept) {

            Response response = validateRqeuestUrI(request);
            if (Objects.nonNull(response)) {
                return response;
            }
        }

        returnMsg = executeAndPrintProcessTime(pjp, requestURI, args, methodName);

        return returnMsg;
    }

    private Response validateRqeuestUrI(HttpServletRequest request) {
        String token = request.getHeader(Constant.USER_TOKEN);
        String key = tokenPrefix + token;
        String requestURI = request.getRequestURI();
        // 代理商请求不拦截
        if (requestURI.startsWith("/wx")) {
            return null;
        }

        String s = redisUtils.get(key);
        User user = userService.getUserById(s);
        if ("Y".equals(user.getAdmin())) {
            return null;
        }

        List<MenuPO> buttons = menuService.getAllPermissionButton(user.getId());
        Map<String, MenuPO> urlMap = buttons.stream().collect(Collectors.toMap(MenuPO::getRequestUrl, Function.identity()));
        if (Objects.isNull(urlMap.get(requestURI))) {
            return Response.error(Constant.SUCCESS_CODE, CommonErrorEnum.NO_AUTHORITY.getCode(), CommonErrorEnum.NO_AUTHORITY.getMessage(), null);
        }
        return null;
    }


    private Response<Object> executeAndPrintProcessTime(ProceedingJoinPoint pjp, String requestURI, Object[] args, String methodName) throws Throwable {
        Response<Object> returnMsg;
        long beginTime = System.currentTimeMillis();
        String className = pjp.getSignature().getDeclaringTypeName();
        returnMsg = (Response<Object>) pjp.proceed(args);
        long endTime = System.currentTimeMillis();
        DecimalFormat df = new DecimalFormat("0.000");
        String timeConsuming = df.format(((float) (endTime - beginTime)) / 1000);
        log.info("接口耗时统计:【访问的类名=" + className + ";URI=" + requestURI + ";耗时=" + timeConsuming + "秒】");
        log.debug("OUT Response------" + methodName + ":" + returnMsg);
        return returnMsg;
    }

    private Response validateToken(HttpServletRequest request) {
        // 获取请求token
        String token = request.getHeader(Constant.USER_TOKEN);
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter(Constant.USER_TOKEN);
        }

        log.info("request token: {}", token);
        if (StringUtils.isEmpty(token)) {
            return Response.error(Constant.SUCCESS_CODE, CommonErrorEnum.NO_AUTHORITY.getCode(), CommonErrorEnum.NO_AUTHORITY.getMessage(), null);
        }

        try {
            HttpSession session = request.getSession();
            String obj = (String) session.getAttribute(Constant.USER_SESSION_KEY);
            if (StringUtils.isEmpty(obj)) {
                String key = env.getProperty("remote.prefix").toString() + token;
                String value = redisUtils.get(key);
                if (StringUtils.isEmpty(value)) {
                    return Response.error(Constant.SUCCESS_CODE, CommonErrorEnum.LOGIN_TIMEOUT.getCode(), CommonErrorEnum.LOGIN_TIMEOUT.getMessage(), null);
                }
                log.info("redis token data value: {}", value);
                session.setMaxInactiveInterval(env.getProperty("remote.expireSeconds", Integer.class));
                session.setAttribute(Constant.USER_SESSION_KEY, value);
                User user = userService.getUserById(value);
                session.setAttribute(Constant.USERINFO_SESSION_KEY, user);
            }
        } catch (JedisException e) {
            log.error("get redisResource failed", e);
            return Response.error(Constant.SUCCESS_CODE, CommonErrorEnum.LOGIN_FAILED.getCode(), CommonErrorEnum.LOGIN_FAILED.getMessage(), null);
        } catch (Exception e) {
            log.error("Analysis user token failed", e);
            return Response.error(Constant.SUCCESS_CODE, CommonErrorEnum.UNKNOWN_ERROR.getCode(), CommonErrorEnum.UNKNOWN_ERROR.getMessage(), null);
        }
        return null;
    }

    /**
     * 是否不需要拦截
     *
     * @param apiUrl
     * @return
     */
    private boolean isUnInterceptor(String apiUrl) {
        Optional<String> first = UN_INTERCEPTOR.stream().filter(uri -> apiUrl.contains(uri)).findFirst();
        if (first.isPresent()) {
            return true;
        }
        return false;
    }
}